import java.nio.channels.*;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.*;
import java.net.*;
import java.util.*;
import java.io.IOException;

/**
 * @author Team 29
 * This singleton is used by the server to communicate with clients.
 * Allows multiple connections with non-blocking sockets.
 *
 */
public class ServerGate {

	int port = 50000;
	Selector select;
	ByteBuffer inbuff= ByteBuffer.allocateDirect(1024);;
	CharsetDecoder decoder = Charset.forName("ISO-8859-1").newDecoder();
	ServerMessageHandler smh;
	
	public ServerGate()
	{
		//smh = new ServerMessageHandler(this);
		try{
			ServerSocketChannel sc = ServerSocketChannel.open();
			sc.configureBlocking(false);
			ServerSocket ss = sc.socket();
			ss.bind(new InetSocketAddress(port));
			select = Selector.open();
			sc.register(select, SelectionKey.OP_ACCEPT);
		}
		catch(IOException e){
			System.out.println("cant open socket");
			Logger.log("ERROR OPENING SOCKET");
			System.exit(-1);
		}
	}//end constructor
	
	/**
	 * @param s
	 * @param target
	 * sends the given message through the given socket.
	 */
	public void send(String s, SocketChannel target)
	{
		ByteBuffer outbuff = ByteBuffer.allocateDirect(1024);
		outbuff.clear();
		outbuff.put(Cryptography.encryptToBytes(s));
		outbuff.flip();
		try{target.register(select, SelectionKey.OP_WRITE);}
		catch(Exception e){System.out.println("closed channel exception"); System.exit(-1);}
		try{
			while(outbuff.hasRemaining()){
				target.write(outbuff);
			}
		}catch(IOException e){System.out.println("SERVER sending problem");}
		try{target.register(select, SelectionKey.OP_READ);}
		catch(Exception e){System.out.println("closed channel exception"); 
		Logger.log("ERROR: CLOSED CHANNEL");
		System.exit(-1);}
	}//end send
	
	/**
	 * Listens for incoming traffic, reads and accepts connections.
	 */
	public void listen()
	{
		RecievedMessage rm = null;
		RecievedMessage temp = null;
        try {
                select.select();
                Iterator iter = select.selectedKeys().iterator();
                SelectionKey key;
                while (iter.hasNext()) {
                    key = (SelectionKey) iter.next();
                    iter.remove();
                    if (key.isAcceptable()) 
                    {accept(key);}
                    else if (key.isReadable()) 
                        {read(key);}
                    }
                
        } 
        catch (IOException e1) {
        	e1.printStackTrace();
        	Logger.log("ERROR: LISTENING");
        	System.exit(-1);
        	}
	}//end listen
	
	/**
	 * @param key
	 * Accepts a connection on the key's channel.
	 */
	private void accept(SelectionKey key)
	{
        ServerSocketChannel server = (ServerSocketChannel) key.channel();
        try {
            SocketChannel channel = server.accept();
            channel.configureBlocking(false);
            channel.register(select, SelectionKey.OP_READ);
        } 
        catch (IOException e) {e.printStackTrace();}
	}//end accept
	
	/**
	 * @param key
	 * Reads inbound traffic on the key's channel.
	 */
	private void read(SelectionKey key)
	{
		SocketChannel channel = (SocketChannel) key.channel();
		String msg = null;
		try{
			int num = channel.read(inbuff); 
			if(num ==-1) return;
			else
				{int pos = inbuff.position();
				int limit = inbuff.limit();
				inbuff.position(0);
				inbuff.limit(pos);
				try{
					CharBuffer cbuff = decoder.decode(inbuff);
					System.out.println("Raw message recieved: " + cbuff.toString());
					msg = Cryptography.decrypt(cbuff.toString());
					smh.forwardMessage(new RecievedMessage(msg, channel));
				}catch(Exception e){e.printStackTrace();}
				inbuff.limit(limit);
				inbuff.position(pos);
				inbuff.clear();
				}
		}
		catch(IOException i){
			key.cancel();
			try {
			channel.close();
		} catch (IOException e) {
			e.printStackTrace();
			Logger.log("ERROR: READING");
			System.exit(-1);
		} }//end catch	
	}//end read
	
}//end class
